// Copyright 2017 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/compiler/js-type-hint-lowering.h"

#include "src/compiler/access-builder.h"
#include "src/compiler/js-graph.h"
#include "src/compiler/operator-properties.h"
#include "src/compiler/simplified-operator.h"
#include "src/feedback-vector.h"
#include "src/type-hints.h"

#include "src/objects-inl.h" // weolar

namespace v8 {
namespace internal {
    namespace compiler {

        namespace {

            bool BinaryOperationHintToNumberOperationHint(
                BinaryOperationHint binop_hint, NumberOperationHint* number_hint)
            {
                switch (binop_hint) {
                case BinaryOperationHint::kSignedSmall:
                    *number_hint = NumberOperationHint::kSignedSmall;
                    return true;
                case BinaryOperationHint::kSignedSmallInputs:
                    *number_hint = NumberOperationHint::kSignedSmallInputs;
                    return true;
                case BinaryOperationHint::kSigned32:
                    *number_hint = NumberOperationHint::kSigned32;
                    return true;
                case BinaryOperationHint::kNumber:
                    *number_hint = NumberOperationHint::kNumber;
                    return true;
                case BinaryOperationHint::kNumberOrOddball:
                    *number_hint = NumberOperationHint::kNumberOrOddball;
                    return true;
                case BinaryOperationHint::kAny:
                case BinaryOperationHint::kNone:
                case BinaryOperationHint::kString:
                case BinaryOperationHint::kBigInt:
                    break;
                }
                return false;
            }

        } // namespace

        class JSSpeculativeBinopBuilder final {
        public:
            JSSpeculativeBinopBuilder(const JSTypeHintLowering* lowering,
                const Operator* op, Node* left, Node* right,
                Node* effect, Node* control, FeedbackSlot slot)
                : lowering_(lowering)
                , op_(op)
                , left_(left)
                , right_(right)
                , effect_(effect)
                , control_(control)
                , slot_(slot)
            {
            }

            BinaryOperationHint GetBinaryOperationHint()
            {
                FeedbackNexus nexus(feedback_vector(), slot_);
                return nexus.GetBinaryOperationFeedback();
            }

            CompareOperationHint GetCompareOperationHint()
            {
                FeedbackNexus nexus(feedback_vector(), slot_);
                return nexus.GetCompareOperationFeedback();
            }

            bool GetBinaryNumberOperationHint(NumberOperationHint* hint)
            {
                return BinaryOperationHintToNumberOperationHint(GetBinaryOperationHint(),
                    hint);
            }

            bool GetCompareNumberOperationHint(NumberOperationHint* hint)
            {
                switch (GetCompareOperationHint()) {
                case CompareOperationHint::kSignedSmall:
                    *hint = NumberOperationHint::kSignedSmall;
                    return true;
                case CompareOperationHint::kNumber:
                    *hint = NumberOperationHint::kNumber;
                    return true;
                case CompareOperationHint::kNumberOrOddball:
                    *hint = NumberOperationHint::kNumberOrOddball;
                    return true;
                case CompareOperationHint::kAny:
                case CompareOperationHint::kNone:
                case CompareOperationHint::kString:
                case CompareOperationHint::kSymbol:
                case CompareOperationHint::kBigInt:
                case CompareOperationHint::kReceiver:
                case CompareOperationHint::kReceiverOrNullOrUndefined:
                case CompareOperationHint::kInternalizedString:
                    break;
                }
                return false;
            }

            const Operator* SpeculativeNumberOp(NumberOperationHint hint)
            {
                switch (op_->opcode()) {
                case IrOpcode::kJSAdd:
                    if (hint == NumberOperationHint::kSignedSmall || hint == NumberOperationHint::kSigned32) {
                        return simplified()->SpeculativeSafeIntegerAdd(hint);
                    } else {
                        return simplified()->SpeculativeNumberAdd(hint);
                    }
                case IrOpcode::kJSSubtract:
                    if (hint == NumberOperationHint::kSignedSmall || hint == NumberOperationHint::kSigned32) {
                        return simplified()->SpeculativeSafeIntegerSubtract(hint);
                    } else {
                        return simplified()->SpeculativeNumberSubtract(hint);
                    }
                case IrOpcode::kJSMultiply:
                    return simplified()->SpeculativeNumberMultiply(hint);
                case IrOpcode::kJSDivide:
                    return simplified()->SpeculativeNumberDivide(hint);
                case IrOpcode::kJSModulus:
                    return simplified()->SpeculativeNumberModulus(hint);
                case IrOpcode::kJSBitwiseAnd:
                    return simplified()->SpeculativeNumberBitwiseAnd(hint);
                case IrOpcode::kJSBitwiseOr:
                    return simplified()->SpeculativeNumberBitwiseOr(hint);
                case IrOpcode::kJSBitwiseXor:
                    return simplified()->SpeculativeNumberBitwiseXor(hint);
                case IrOpcode::kJSShiftLeft:
                    return simplified()->SpeculativeNumberShiftLeft(hint);
                case IrOpcode::kJSShiftRight:
                    return simplified()->SpeculativeNumberShiftRight(hint);
                case IrOpcode::kJSShiftRightLogical:
                    return simplified()->SpeculativeNumberShiftRightLogical(hint);
                default:
                    break;
                }
                UNREACHABLE();
            }

            const Operator* SpeculativeCompareOp(NumberOperationHint hint)
            {
                switch (op_->opcode()) {
                case IrOpcode::kJSEqual:
                    return simplified()->SpeculativeNumberEqual(hint);
                case IrOpcode::kJSLessThan:
                    return simplified()->SpeculativeNumberLessThan(hint);
                case IrOpcode::kJSGreaterThan:
                    std::swap(left_, right_); // a > b => b < a
                    return simplified()->SpeculativeNumberLessThan(hint);
                case IrOpcode::kJSLessThanOrEqual:
                    return simplified()->SpeculativeNumberLessThanOrEqual(hint);
                case IrOpcode::kJSGreaterThanOrEqual:
                    std::swap(left_, right_); // a >= b => b <= a
                    return simplified()->SpeculativeNumberLessThanOrEqual(hint);
                default:
                    break;
                }
                UNREACHABLE();
            }

            Node* BuildSpeculativeOperation(const Operator* op)
            {
                DCHECK_EQ(2, op->ValueInputCount());
                DCHECK_EQ(1, op->EffectInputCount());
                DCHECK_EQ(1, op->ControlInputCount());
                DCHECK_EQ(false, OperatorProperties::HasFrameStateInput(op));
                DCHECK_EQ(false, OperatorProperties::HasContextInput(op));
                DCHECK_EQ(1, op->EffectOutputCount());
                DCHECK_EQ(0, op->ControlOutputCount());
                return graph()->NewNode(op, left_, right_, effect_, control_);
            }

            Node* TryBuildNumberBinop()
            {
                NumberOperationHint hint;
                if (GetBinaryNumberOperationHint(&hint)) {
                    const Operator* op = SpeculativeNumberOp(hint);
                    Node* node = BuildSpeculativeOperation(op);
                    return node;
                }
                return nullptr;
            }

            Node* TryBuildNumberCompare()
            {
                NumberOperationHint hint;
                if (GetCompareNumberOperationHint(&hint)) {
                    const Operator* op = SpeculativeCompareOp(hint);
                    Node* node = BuildSpeculativeOperation(op);
                    return node;
                }
                return nullptr;
            }

            JSGraph* jsgraph() const { return lowering_->jsgraph(); }
            Isolate* isolate() const { return jsgraph()->isolate(); }
            Graph* graph() const { return jsgraph()->graph(); }
            JSOperatorBuilder* javascript() { return jsgraph()->javascript(); }
            SimplifiedOperatorBuilder* simplified() { return jsgraph()->simplified(); }
            CommonOperatorBuilder* common() { return jsgraph()->common(); }
            const Handle<FeedbackVector>& feedback_vector() const
            {
                return lowering_->feedback_vector();
            }

        private:
            const JSTypeHintLowering* lowering_;
            const Operator* op_;
            Node* left_;
            Node* right_;
            Node* effect_;
            Node* control_;
            FeedbackSlot slot_;
        };

        JSTypeHintLowering::JSTypeHintLowering(JSGraph* jsgraph,
            Handle<FeedbackVector> feedback_vector,
            Flags flags)
            : jsgraph_(jsgraph)
            , flags_(flags)
            , feedback_vector_(feedback_vector)
        {
        }

        Isolate* JSTypeHintLowering::isolate() const { return jsgraph()->isolate(); }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceUnaryOperation(
            const Operator* op, Node* operand, Node* effect, Node* control,
            FeedbackSlot slot) const
        {
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForUnaryOperation)) {
                return LoweringResult::Exit(node);
            }

            Node* node;
            switch (op->opcode()) {
            case IrOpcode::kJSBitwiseNot: {
                // Lower to a speculative xor with -1 if we have some kind of Number
                // feedback.
                JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->BitwiseXor(),
                    operand, jsgraph()->SmiConstant(-1), effect,
                    control, slot);
                node = b.TryBuildNumberBinop();
                break;
            }
            case IrOpcode::kJSDecrement: {
                // Lower to a speculative subtraction of 1 if we have some kind of Number
                // feedback.
                JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->Subtract(),
                    operand, jsgraph()->SmiConstant(1), effect,
                    control, slot);
                node = b.TryBuildNumberBinop();
                break;
            }
            case IrOpcode::kJSIncrement: {
                // Lower to a speculative addition of 1 if we have some kind of Number
                // feedback.
                BinaryOperationHint hint = BinaryOperationHint::kAny; // Dummy.
                JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->Add(hint),
                    operand, jsgraph()->SmiConstant(1), effect,
                    control, slot);
                node = b.TryBuildNumberBinop();
                break;
            }
            case IrOpcode::kJSNegate: {
                // Lower to a speculative multiplication with -1 if we have some kind of
                // Number feedback.
                JSSpeculativeBinopBuilder b(this, jsgraph()->javascript()->Multiply(),
                    operand, jsgraph()->SmiConstant(-1), effect,
                    control, slot);
                node = b.TryBuildNumberBinop();
                break;
            }
            default:
                UNREACHABLE();
                break;
            }

            if (node != nullptr) {
                return LoweringResult::SideEffectFree(node, node, control);
            } else {
                return LoweringResult::NoChange();
            }
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceBinaryOperation(
            const Operator* op, Node* left, Node* right, Node* effect, Node* control,
            FeedbackSlot slot) const
        {
            switch (op->opcode()) {
            case IrOpcode::kJSStrictEqual: {
                DCHECK(!slot.IsInvalid());
                FeedbackNexus nexus(feedback_vector(), slot);
                if (Node* node = TryBuildSoftDeopt(
                        nexus, effect, control,
                        DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation)) {
                    return LoweringResult::Exit(node);
                }
                // TODO(turbofan): Should we generally support early lowering of
                // JSStrictEqual operators here?
                break;
            }
            case IrOpcode::kJSEqual:
            case IrOpcode::kJSLessThan:
            case IrOpcode::kJSGreaterThan:
            case IrOpcode::kJSLessThanOrEqual:
            case IrOpcode::kJSGreaterThanOrEqual: {
                DCHECK(!slot.IsInvalid());
                FeedbackNexus nexus(feedback_vector(), slot);
                if (Node* node = TryBuildSoftDeopt(
                        nexus, effect, control,
                        DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation)) {
                    return LoweringResult::Exit(node);
                }
                JSSpeculativeBinopBuilder b(this, op, left, right, effect, control, slot);
                if (Node* node = b.TryBuildNumberCompare()) {
                    return LoweringResult::SideEffectFree(node, node, control);
                }
                break;
            }
            case IrOpcode::kJSInstanceOf: {
                DCHECK(!slot.IsInvalid());
                FeedbackNexus nexus(feedback_vector(), slot);
                if (Node* node = TryBuildSoftDeopt(
                        nexus, effect, control,
                        DeoptimizeReason::kInsufficientTypeFeedbackForCompareOperation)) {
                    return LoweringResult::Exit(node);
                }
                // TODO(turbofan): Should we generally support early lowering of
                // JSInstanceOf operators here?
                break;
            }
            case IrOpcode::kJSBitwiseOr:
            case IrOpcode::kJSBitwiseXor:
            case IrOpcode::kJSBitwiseAnd:
            case IrOpcode::kJSShiftLeft:
            case IrOpcode::kJSShiftRight:
            case IrOpcode::kJSShiftRightLogical:
            case IrOpcode::kJSAdd:
            case IrOpcode::kJSSubtract:
            case IrOpcode::kJSMultiply:
            case IrOpcode::kJSDivide:
            case IrOpcode::kJSModulus: {
                DCHECK(!slot.IsInvalid());
                FeedbackNexus nexus(feedback_vector(), slot);
                if (Node* node = TryBuildSoftDeopt(
                        nexus, effect, control,
                        DeoptimizeReason::kInsufficientTypeFeedbackForBinaryOperation)) {
                    return LoweringResult::Exit(node);
                }
                JSSpeculativeBinopBuilder b(this, op, left, right, effect, control, slot);
                if (Node* node = b.TryBuildNumberBinop()) {
                    return LoweringResult::SideEffectFree(node, node, control);
                }
                break;
            }
            case IrOpcode::kJSExponentiate: {
                // TODO(neis): Introduce a SpeculativeNumberPow operator?
                break;
            }
            default:
                UNREACHABLE();
                break;
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceForInNextOperation(
            Node* receiver, Node* cache_array, Node* cache_type, Node* index,
            Node* effect, Node* control, FeedbackSlot slot) const
        {
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForForIn)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult
        JSTypeHintLowering::ReduceForInPrepareOperation(Node* enumerator, Node* effect,
            Node* control,
            FeedbackSlot slot) const
        {
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForForIn)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceToNumberOperation(
            Node* input, Node* effect, Node* control, FeedbackSlot slot) const
        {
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            NumberOperationHint hint;
            if (BinaryOperationHintToNumberOperationHint(
                    nexus.GetBinaryOperationFeedback(), &hint)) {
                Node* node = jsgraph()->graph()->NewNode(
                    jsgraph()->simplified()->SpeculativeToNumber(hint, VectorSlotPair()),
                    input, effect, control);
                return LoweringResult::SideEffectFree(node, node, control);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceCallOperation(
            const Operator* op, Node* const* args, int arg_count, Node* effect,
            Node* control, FeedbackSlot slot) const
        {
            DCHECK(op->opcode() == IrOpcode::kJSCall || op->opcode() == IrOpcode::kJSCallWithSpread);
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForCall)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceConstructOperation(
            const Operator* op, Node* const* args, int arg_count, Node* effect,
            Node* control, FeedbackSlot slot) const
        {
            DCHECK(op->opcode() == IrOpcode::kJSConstruct || op->opcode() == IrOpcode::kJSConstructWithSpread);
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForConstruct)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceLoadNamedOperation(
            const Operator* op, Node* receiver, Node* effect, Node* control,
            FeedbackSlot slot) const
        {
            DCHECK_EQ(IrOpcode::kJSLoadNamed, op->opcode());
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult JSTypeHintLowering::ReduceLoadKeyedOperation(
            const Operator* op, Node* obj, Node* key, Node* effect, Node* control,
            FeedbackSlot slot) const
        {
            DCHECK_EQ(IrOpcode::kJSLoadProperty, op->opcode());
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult
        JSTypeHintLowering::ReduceStoreNamedOperation(const Operator* op, Node* obj,
            Node* val, Node* effect,
            Node* control,
            FeedbackSlot slot) const
        {
            DCHECK(op->opcode() == IrOpcode::kJSStoreNamed || op->opcode() == IrOpcode::kJSStoreNamedOwn);
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForGenericNamedAccess)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        JSTypeHintLowering::LoweringResult
        JSTypeHintLowering::ReduceStoreKeyedOperation(const Operator* op, Node* obj,
            Node* key, Node* val,
            Node* effect, Node* control,
            FeedbackSlot slot) const
        {
            DCHECK(op->opcode() == IrOpcode::kJSStoreProperty || op->opcode() == IrOpcode::kJSStoreInArrayLiteral);
            DCHECK(!slot.IsInvalid());
            FeedbackNexus nexus(feedback_vector(), slot);
            if (Node* node = TryBuildSoftDeopt(
                    nexus, effect, control,
                    DeoptimizeReason::kInsufficientTypeFeedbackForGenericKeyedAccess)) {
                return LoweringResult::Exit(node);
            }
            return LoweringResult::NoChange();
        }

        Node* JSTypeHintLowering::TryBuildSoftDeopt(FeedbackNexus& nexus, Node* effect,
            Node* control,
            DeoptimizeReason reason) const
        {
            if ((flags() & kBailoutOnUninitialized) && nexus.IsUninitialized()) {
                Node* deoptimize = jsgraph()->graph()->NewNode(
                    jsgraph()->common()->Deoptimize(DeoptimizeKind::kSoft, reason,
                        VectorSlotPair()),
                    jsgraph()->Dead(), effect, control);
                Node* frame_state = NodeProperties::FindFrameStateBefore(deoptimize);
                deoptimize->ReplaceInput(0, frame_state);
                return deoptimize;
            }
            return nullptr;
        }

    } // namespace compiler
} // namespace internal
} // namespace v8
